package com.tiancheng.trade.order.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tiancheng.trade.commom.core.data.Result;
import com.tiancheng.trade.commom.core.enums.VerificationStatusEnum;
import com.tiancheng.trade.commom.core.exception.BusinessException;
import com.tiancheng.trade.commom.core.exception.error.BusinessErrorInfoEnum;
import com.tiancheng.trade.commom.core.utils.JsonUtil;
import com.tiancheng.trade.commom.web.model.request.RefundVerificationRequestVO;
import com.tiancheng.trade.commom.web.model.response.RefundOrderResVO;
import com.tiancheng.trade.order.bo.RefundNotifyBO;
import com.tiancheng.trade.order.bo.RefundOrdersDetailSubmitBO;
import com.tiancheng.trade.order.bo.SubOrderRefundBO;
import com.tiancheng.trade.order.channel.AbstractPayChannel;
import com.tiancheng.trade.order.channel.PayChannelFactory;
import com.tiancheng.trade.order.enums.OrderStatusEnum;
import com.tiancheng.trade.order.enums.OrderTypeEnum;
import com.tiancheng.trade.order.enums.RefundStatusEnum;
import com.tiancheng.trade.commom.web.feign.PaymentFeignService;
import com.tiancheng.trade.commom.web.model.request.RefundDTO;
import com.tiancheng.trade.order.mapper.RefundOrdersMapper;
import com.tiancheng.trade.order.model.Order;
import com.tiancheng.trade.order.model.RefundOrders;
import com.tiancheng.trade.order.model.RefundOrdersDetail;
import com.tiancheng.trade.order.service.OrderService;
import com.tiancheng.trade.order.service.RefundOrdersDetailService;
import com.tiancheng.trade.order.service.RefundOrdersService;
import com.tiancheng.trade.order.service.SubOrderService;
import com.tiancheng.trade.order.util.lock.Lock;
import com.tiancheng.trade.order.vo.api.RefundAuditVO;
import com.tiancheng.trade.order.vo.open.order.OpenRefundCreateResVO;
import com.tiancheng.trade.order.vo.open.order.OpenRefundVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @Author: kellen
 * @Description:
 * @Date: create in 2024/11/26 20:15
 */
@Slf4j
@Service
public class RefundOrdersServiceImpl extends ServiceImpl<RefundOrdersMapper, RefundOrders> implements RefundOrdersService {

    @Resource
    private OrderService orderService;
    @Resource
    private PaymentFeignService paymentFeignService;
    @Resource
    private SubOrderService subOrderService;
    @Resource
    private RefundOrdersDetailService refundOrdersDetailService;

    @Override
    @Transactional
    public OpenRefundCreateResVO createAndSubmit(OpenRefundVO refundVO) throws BusinessException {
        Order order = orderService.get(refundVO.getUid(), refundVO.getOrderId());
        BusinessErrorInfoEnum.ORDER_REFUND_NO_DATA.assertNotNull(order);
        BusinessErrorInfoEnum.ORDER_REFUND_STATUS_ERROR.assertIsTrue(OrderStatusEnum.paid.getCode().equals(order.getOrderStatus()));

        // 针对该用户加锁，5秒后自动释放，并发等待5秒获取，5秒后未获取抛出超时异常
        try (Lock lock = new Lock(Lock.LOCK_ORDER_REFUND, refundVO.getOrderId(), 5, 5)) {
            BusinessErrorInfoEnum.ORDER_REFUND_TIMEOUT.assertIsTrue(lock.getLockRes());

            AbstractPayChannel channel = PayChannelFactory.get(order.getPayChannel());
            // 生成退款参数
            final RefundOrders refundOrders = channel.generateRefunds(refundVO, order);
            RefundDTO refundDTO = channel.createAndSubmit(refundVO, refundOrders);
            // 调用支付中心退款
            Result<RefundOrderResVO> result ;
            if(order.getOrderType().equalsIgnoreCase(OrderTypeEnum.deduction.getCode()) || (order.getOrderAmount().equals(order.getPlatformDiscount()))){
                // 全额抵扣，或者积分抵扣的订单
                result= paymentFeignService.deductionRefund(refundDTO);
            }else{
                // 正常退款
                result= paymentFeignService.refundOrder(refundDTO);
            }

            log.info("退款单号:{},退款返回信息:{}", refundDTO.getPaymentId(), result);
            BusinessErrorInfoEnum.ORDER_REFUND_SERVER_ERROR.assertIsTrue(result.getRet() == 0 && result.getErrorCode() == 0, StringUtils.isNotEmpty(result.getMsg())?result.getMsg():"");

            // 修改子订单状态退款中
            String[] subOrderIds = refundVO.getSubOrders().stream().map(OpenRefundVO.SubOrder::getSubOrderId).toArray(String[]::new);
            subOrderService.refundIng(subOrderIds);
            final RefundOrderResVO data = result.getData();
            String flowId = String.valueOf(data.getFlowId());
            // 实际退款金额
            //int amount = Integer.parseInt(String.valueOf(dataMap.get("amount")));
            final List<RefundOrdersDetail> refundOrdersDetails = refundOrders.getSubRefundOrders();
            refundOrdersDetails.forEach(r -> r.setFlowId(flowId));
            // 保存退款记录
            refundOrdersDetailService.saveBatch(refundOrdersDetails);
            this.save(refundOrders);

            OpenRefundCreateResVO resVO = new OpenRefundCreateResVO();
            resVO.setOrderId(order.getOrderId());
            resVO.setPaymentId(refundOrders.getPaymentId());
            resVO.setRefundId(refundOrders.getRefundId());
            refundOrdersDetails.forEach(refund -> resVO.getSubOrders().add(new OpenRefundCreateResVO.SubOrder(refund.getSubOrderId(),
                    refund.getSubRefundId(),refund.getSubPaymentId(),
                    refund.getRefundApplyAmount(),  refund.getRefundAmount())));

            return resVO;
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            throw new BusinessException(BusinessErrorInfoEnum.ORDER_REFUND_TIMEOUT, null, e.getMessage(), e);
        }
    }


    @Override
    @Transactional
    public List<RefundOrdersDetail> saveRefundResult(RefundNotifyBO refundNotifyBO) throws BusinessException {
        boolean success = "success".equalsIgnoreCase(refundNotifyBO.getStatus());

        final RefundOrders refundOrders = this.getOne(new LambdaQueryWrapper<RefundOrders>().eq(RefundOrders::getPaymentId, refundNotifyBO.getPaymentId()));
        BusinessErrorInfoEnum.ORDER_REFUND_NO_DATA.assertIsTrue(Objects.nonNull(refundOrders) && Objects.nonNull(refundOrders.getId()));

        QueryWrapper<RefundOrdersDetail> conditions = new QueryWrapper<>();
//        conditions.eq("flow_id", refundNotifyBO.getFlowId());
        conditions.eq("payment_id", refundNotifyBO.getPaymentId());
        //conditions.eq("uid", refundNotifyBO.getUserId());
        List<RefundOrdersDetail> refundOrdersDetails = refundOrdersDetailService.list(conditions);
        BusinessErrorInfoEnum.ORDER_REFUND_NO_DATA.assertIsTrue(refundOrdersDetails.size() > 0);

        if (refundOrdersDetails.get(0).getRefundStatus().equals(RefundStatusEnum.success.getCode())) {
            BusinessErrorInfoEnum.ORDER_REFUND_REPEAT_NOTICE.assertFail();
        }
        int refundAmount = refundOrdersDetails.stream().mapToInt(RefundOrdersDetail::getRefundAmount).sum();
        // 实退金额可能会有差异
        //BusinessErrorInfoEnum.ORDER_REFUND_AMOUNT_DIFF.assertIsTrue(refundAmount == refundNotifyBO.getAmount());
        // 如果实际退款金额不一样，则需要重新修正退款金额
        if (refundAmount != refundNotifyBO.getAmount()) {
            log.info("退款回调，实退金额与申请金额不一致，申请：{}，实退：{}", refundAmount, refundNotifyBO.getAmount());
            RefundAuditVO refundAuditVO = new RefundAuditVO();
            refundAuditVO.setOrderId(refundOrdersDetails.get(0).getOrderId());
            refundAuditVO.setPaymentId(refundNotifyBO.getPaymentId());
            this.extracted(refundAuditVO, refundOrdersDetails, refundNotifyBO.getFlowId(), refundNotifyBO.getAmount());
        }
        RefundOrdersDetail updateRefundOrdersDetail = new RefundOrdersDetail();
        updateRefundOrdersDetail.setFlowId(refundNotifyBO.getFlowId());
        updateRefundOrdersDetail.setRefundTime(refundNotifyBO.getRefundTime());
        updateRefundOrdersDetail.setRefundStatus(success ? RefundStatusEnum.success.getCode() : RefundStatusEnum.fail.getCode());
        refundOrdersDetailService.update(updateRefundOrdersDetail, conditions);

        RefundOrders updateRefundOrders=new RefundOrders();
        updateRefundOrders.setId(refundOrders.getId());
        updateRefundOrders.setRefundAmount(refundNotifyBO.getAmount());
        updateRefundOrders.setLastUpdDt(LocalDateTime.now());
        updateRefundOrders.setFlowId(refundNotifyBO.getFlowId());
        updateRefundOrders.setRefundStatus(updateRefundOrdersDetail.getRefundStatus());
        this.updateById(updateRefundOrders);
        // 修改订单状态
        String orderId = refundOrdersDetails.get(0).getOrderId();
        List<SubOrderRefundBO> subOrderRefundBOS = new ArrayList<>();
        for (RefundOrdersDetail refundOrdersDetail : refundOrdersDetails) {
            String refundStatus = success ? RefundStatusEnum.success.getCode() : RefundStatusEnum.fail.getCode();
            Integer subOrderRefundAmount = success ? refundOrdersDetail.getRefundAmount() : 0;
            subOrderRefundBOS.add(new SubOrderRefundBO(orderId, refundOrdersDetail.getSubOrderId(), subOrderRefundAmount, refundStatus));
        }
        orderService.refund(success, orderId, subOrderRefundBOS);

        refundOrdersDetails.forEach(refund -> {
            refund.setRefundTime(updateRefundOrdersDetail.getRefundTime());
            refund.setRefundStatus(updateRefundOrdersDetail.getRefundStatus());
            refund.setFlowId(refundNotifyBO.getFlowId());
        });
        return refundOrdersDetails;
    }

    @Override
    public RefundOrders getByRefundId(String refundId) {
        return this.getOne(new LambdaQueryWrapper<RefundOrders>().eq(RefundOrders::getRefundId,refundId));
    }

    /**
     * 修改实际退款金额
     *
     * @param refundAuditVO
     * @param refundOrdersDetails       对款流水
     * @param flowId        退款流水号
     * @param amount        总的实退金额
     */
    private void extracted(RefundAuditVO refundAuditVO, List<RefundOrdersDetail> refundOrdersDetails, String flowId, int amount) {
        // 实际退款金额
        int totalAmount = refundOrdersDetails.stream().mapToInt(RefundOrdersDetail::getRefundApplyAmount).sum();
        List<RefundOrdersDetailSubmitBO> refundOrdersDetailSubmitBOS = new ArrayList<>(refundOrdersDetails.size());
        // 累计实退金额
        int refundedAmount = 0;
        for (int i = 0; i < refundOrdersDetails.size(); i++) {
            RefundOrdersDetail refundOrdersDetail = refundOrdersDetails.get(i);
            int refundInvoiceAmount = 0;
            int discount = 0;//优惠总金额=平台优惠金额+渠道优惠金额
            if (i == refundOrdersDetails.size() - 1) {
                refundInvoiceAmount = (amount - refundedAmount);
            } else {
                // 1、计算子订单所占总金额的比例，小数点后取10位，然后截取
                // 2、总优惠金额*子订单比例，然后四舍五入取整
                int divide = BigDecimal.valueOf(refundOrdersDetail.getRefundApplyAmount()).
                        divide(BigDecimal.valueOf(totalAmount), 10, RoundingMode.DOWN)
                        .multiply(BigDecimal.valueOf(amount)).setScale(0, RoundingMode.HALF_UP).intValue();
                // 如果平摊后的实退金额大于支付金额，则设置实退金额=支付金额
                if (divide > refundOrdersDetail.getSubOrderPayAmount()) {
                    divide = refundOrdersDetail.getSubOrderPayAmount();
                }
                refundInvoiceAmount = divide;
                refundedAmount += divide;
            }
            discount = refundOrdersDetail.getPlatformDiscount() + (refundOrdersDetails.get(i).getRefundAmount() - refundInvoiceAmount);
            refundOrdersDetailSubmitBOS.add(new RefundOrdersDetailSubmitBO(refundAuditVO.getOrderId(), refundAuditVO.getPaymentId(),
                    refundOrdersDetail.getSubOrderId(), flowId, refundInvoiceAmount, refundOrdersDetail.getRefundApplyAmount(), discount, null, LocalDateTime.now()));
            log.info("修改子订单: {}实退金额，申请：{}，申请实退：{}，最后实退：{}，平台优惠：{}，总优惠：{}", refundOrdersDetail.getSubOrderId(), refundOrdersDetail.getRefundApplyAmount(), refundOrdersDetail.getRefundAmount(), refundInvoiceAmount, refundOrdersDetail.getPlatformDiscount(), discount);
            // 设置实退金额
            refundOrdersDetails.get(i).setRefundAmount(refundInvoiceAmount);
            refundOrdersDetails.get(i).setDiscount(discount);
        }
        refundOrdersDetailService.updateBatch(refundOrdersDetailSubmitBOS);
    }
}
